 /*******************************************************************************
  * Copyright (c) 2003, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.osgi.framework.internal.protocol;

 import java.lang.reflect.Method ;
 import java.net.ContentHandler ;
 import java.net.URLConnection ;
 import java.util.*;
 import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
 import org.eclipse.osgi.framework.internal.core.Msg;
 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
 import org.eclipse.osgi.util.NLS;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.url.URLConstants;
 import org.osgi.util.tracker.ServiceTracker;

 /**
  * The ContentHandlerFactory is registered with the JVM to provide content handlers
  * to requestors. The ContentHandlerFactory will first look for built-in content handlers.
  * If a built in handler exists, this factory will return null. Otherwise, this ContentHandlerFactory
  * will search the service registry for a maching Content-Handler and, if found, return a
  * proxy for that content handler.
  */
 // TODO rename this class!!! its really confusing to name the impl the same as the interface
 public class ContentHandlerFactory extends MultiplexingFactory implements java.net.ContentHandlerFactory {
     private ServiceTracker contentHandlerTracker;

     private static final String contentHandlerClazz = "java.net.ContentHandler"; //$NON-NLS-1$
 private static final String CONTENT_HANDLER_PKGS = "java.content.handler.pkgs"; //$NON-NLS-1$
 private static final String DEFAULT_VM_CONTENT_HANDLERS = "sun.net.www.content"; //$NON-NLS-1$

     private static final List ignoredClasses = Arrays.asList(new Class [] {MultiplexingContentHandler.class, ContentHandlerFactory.class, URLConnection .class});

     private Hashtable proxies;
     private java.net.ContentHandlerFactory parentFactory;

     public ContentHandlerFactory(BundleContext context, FrameworkAdaptor adaptor) {
         super(context, adaptor);

         proxies = new Hashtable(5);

         //We need to track content handler registrations
 contentHandlerTracker = new ServiceTracker(context, contentHandlerClazz, null);
         contentHandlerTracker.open();
     }

     /**
      * @see java.net.ContentHandlerFactory#createContentHandler(String)
      */
     //TODO method is too long... consider reducing indentation (returning quickly) and moving complex steps to private methods
 public ContentHandler createContentHandler(String contentType) {
         //first, we check to see if there exists a built in content handler for
 //this content type. we can not overwrite built in ContentHandlers
 String builtInHandlers = StreamHandlerFactory.secureAction.getProperty(CONTENT_HANDLER_PKGS);
         builtInHandlers = builtInHandlers == null ? DEFAULT_VM_CONTENT_HANDLERS : DEFAULT_VM_CONTENT_HANDLERS + '|' + builtInHandlers;
         Class clazz = null;
         if (builtInHandlers != null) {
             //replace '/' with a '.' and all characters not allowed in a java class name
 //with a '_'.

             // find all characters not allowed in java names
 String convertedContentType = contentType.replace('.', '_');
             convertedContentType = convertedContentType.replace('/', '.');
             convertedContentType = convertedContentType.replace('-', '_');
             StringTokenizer tok = new StringTokenizer(builtInHandlers, "|"); //$NON-NLS-1$
 while (tok.hasMoreElements()) {
                 StringBuffer name = new StringBuffer ();
                 name.append(tok.nextToken());
                 name.append("."); //$NON-NLS-1$
 name.append(convertedContentType);
                 try {
                     clazz = StreamHandlerFactory.secureAction.loadSystemClass(name.toString());
                     if (clazz != null) {
                         return (null); //this class exists, it is a built in handler, let the JVM handle it
 }
                 } catch (ClassNotFoundException ex) {
                     //keep looking
 }
             }
         }

         if (isMultiplexing())
             return new MultiplexingContentHandler(contentType, this);

         return createInternalContentHandler(contentType);
     }

     public ContentHandler createInternalContentHandler(String contentType) {
         //first check to see if the handler is in the cache
 ContentHandlerProxy proxy = (ContentHandlerProxy) proxies.get(contentType);
         if (proxy != null) {
             return (proxy);
         }
         org.osgi.framework.ServiceReference[] serviceReferences = contentHandlerTracker.getServiceReferences();
         if (serviceReferences != null) {
             for (int i = 0; i < serviceReferences.length; i++) {
                 Object prop = serviceReferences[i].getProperty(URLConstants.URL_CONTENT_MIMETYPE);
                 if (prop instanceof String )
                     prop = new String [] {(String ) prop}; // TODO should this be a warning?
 if (!(prop instanceof String [])) {
                     String message = NLS.bind(Msg.URL_HANDLER_INCORRECT_TYPE, new Object [] {URLConstants.URL_CONTENT_MIMETYPE, contentHandlerClazz, serviceReferences[i].getBundle()});
                     adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, message, 0, null, null));
                     continue;
                 }
                 String [] contentHandler = (String []) prop;
                 for (int j = 0; j < contentHandler.length; j++) {
                     if (contentHandler[j].equals(contentType)) {
                         proxy = new ContentHandlerProxy(contentType, serviceReferences[i], context);
                         proxies.put(contentType, proxy);
                         return (proxy);
                     }
                 }
             }
         }
         // if parent is present do parent lookup before returning a proxy
 if (parentFactory != null) {
             ContentHandler parentHandler = parentFactory.createContentHandler(contentType);
             if (parentHandler != null)
                 return parentHandler;
         }
         //If we can't find the content handler in the service registry, return Proxy with DefaultContentHandler set.
 //We need to do this because if we return null, we won't get called again for this content type.
 proxy = new ContentHandlerProxy(contentType, null, context);
         proxies.put(contentType, proxy);
         return (proxy);
     }

     public synchronized ContentHandler findAuthorizedContentHandler(String contentType) {
         Object factory = findAuthorizedFactory(ignoredClasses);
         if (factory == null)
             return null;

         if (factory == this)
             return createInternalContentHandler(contentType);

         try {
             Method createInternalContentHandlerMethod = factory.getClass().getMethod("createInternalContentHandler", new Class [] {String .class}); //$NON-NLS-1$
 return (ContentHandler ) createInternalContentHandlerMethod.invoke(factory, new Object [] {contentType});
         } catch (Exception e) {
             adaptor.getFrameworkLog().log(new FrameworkLogEntry(ContentHandlerFactory.class.getName(), "findAuthorizedContentHandler-loop", FrameworkLogEntry.ERROR, e, null)); //$NON-NLS-1$
 throw new RuntimeException (e.getMessage());
         }
     }

     public Object getParentFactory() {
         return parentFactory;
     }

     public void setParentFactory(Object parentFactory) {
         if (this.parentFactory == null) // only allow it to be set once
 this.parentFactory = (java.net.ContentHandlerFactory ) parentFactory;
     }
 }

